# -*- coding:utf-8 -*-
"""
操作数据库的类。
这里使用的是原生的SQL，没有使用Django的ORM, 也没有使用SqlAlchemy的ORM

建立和数据库的连接，并进行操作。
一般使用sqlalchemy连接与操作数据库，会有这么几种状态
（1）建立了连接，engine,然后conn = engine.connect()
（2）建立了会话，session=sessionmaker(engine)(),不过这样需要映射出的数据表才能用
（3）映射出数据表，通过映射的数据表
"""
__author__ = "sunsn"

import os
import traceback
import logging

import pandas as pd
# import sqlalchemy.pool as pool
from sqlalchemy import create_engine, MetaData, Table
# from sqlalchemy.orm import sessionmaker

logger = logging.getLogger("proj")
os.environ['NLS_LANG'] = 'SIMPLIFIED CHINESE_CHINA.AL32UTF8'


class DbOper(object):
    '''
    根据传入的数据库连接的不同创建数据库连接

    推荐使用with上下文语句调用。。。
    否则在程序中调用的话，一定要放在try…except…finally
    在finally语句中调用close_resource方法关闭资源
    '''
    def __init__(self, db_url, poolsize=1):
        self.db_url = db_url
        self._logger = logger
        self._create_connection(poolsize)

    def _create_connection(self, poolsize):
        try:
            # self.engine = create_engine(self.db_url, poolclass=pool.NullPool, echo=False)
            self._engine = create_engine(self.db_url, pool_size=poolsize, pool_recycle=600, pool_pre_ping=True, echo=False)
            # 下面都和engine进行了绑定
            # 创建meta信息
            self._metadata = MetaData(self._engine)
            # 创建与数据的会话
            # Session = sessionmaker(bind=self.engine)
            # self.session = Session()
            # 创建连接
            # self.conn = self.engine.connect()
        except Exception:
            info = "创建数据库连接失败。\n" + traceback.format_exc()
            self._logger.error(info)
            raise Exception(info)

    def reflect_table(self, schema: str, table_name: str) -> object:
        """
        反射表，将数据库中的表，反射为Python的对象
        :param schema: 数据库的schema名称
        :param table_name: schema下的表名
        :return:
            返回的是数据表的python对象
        """
        # 因为创建metadata实例的时候已经绑定了engine,所以这里就不需要设置参数autoload_with=engine了
        try:
            obj_table = Table(table_name, self._metadata, autoload=True, schema=schema)
            return obj_table
        except Exception:
            info = "反射表出错。\n" + traceback.format_exc()
            raise Exception(info)

    def read_sql2df(self, ssql: str) -> dict:
        """
        执行sql语句，生成dataframe。并封装在一个字典里返回

        :param ssql:  sql语句
        :type ssql: `str`
        :return:
            返回一个字典对象。包含status、result、info
        :rtype: `dict`
        """
        assert ssql is not None, "查询语句(ssql)不能为空"
        assert isinstance(ssql, str), "查询语句(ssql)必须是字符串"

        ssql = ssql.strip().lower()
        if not ssql.startswith("select"):
            raise Exception("只支持select语句")

        conn = self._engine.connect()
        try:
            df = pd.read_sql(ssql, con=conn)
            info = "获取数据%s条" % (len(df))
            return {'status': True, 'result': df, 'info': info}
        except Exception:
            info = "查询数据库并保存为pd.DataFrame失败。\n" + traceback.format_exc()
            self._logger.error(info)
            # raise Exception(info)
            return {'status': False, 'result': None, 'info': info}
        finally:
            conn.close()

    def execute_query(self, ssql: str) -> dict:
        """
        执行sql语句，生成dataframe。并封装在一个字典里返回

        :param ssql:  sql语句
        :type ssql: `str`
        :return:
            返回一个字典对象。包含status、result、info
        :rtype: `dict`
        """
        assert ssql is not None, "查询语句(ssql)不能为空"
        assert isinstance(ssql, str), "查询语句(ssql)必须是字符串"

        ssql = ssql.strip().lower()
        if not ssql.startswith(("select", "update")):
            raise ValueError("execute_query函数只能执行select,update语句。")

        conn = self._engine.connect()
        try:
            if "select" in ssql:
                result = conn.execute(ssql).fetchall()
                info = f"获取数据%s条, execute_query函数执行语句为：{ssql}" % (len(result))
            elif "update" in ssql:
                if "where" not in ssql:
                    raise ValueError("execute_query函数执行update语句时，必须带上where条件")
                result = conn.execute(ssql)
                info = f"更新数据成功, execute_query函数执行语句为：{ssql}"
            else:
                result = []
                info = ""
            # 获取所有结果
            return {'status': True, 'result': result, 'info': info}
        except Exception as e:
            info = 'execute_query执行{ssql}报错,错误信息{info}'.format(ssql=ssql, info=traceback.format_exc())
            self._logger.error(info)
            return {'status': False, 'result': [], 'info': info}
        finally:
            conn.close()

    def insert_data(self, schema: str, table_name: str, data_lst: list) -> dict:
        """

        :param schema:
        :param table_name:
        :param data_lst:
        :return:
        """
        assert isinstance(data_lst, (list, tuple)), "请遵循接口规则，传入一个list/tuple, 元素为字典"

        conn = self._engine.connect()
        try:
            obj_tb = self.reflect_table(schema, table_name)
            insert_sql = obj_tb.insert()
            # 使用上下文，实现事务功能
            with conn.begin() as trans:
                conn.execute(insert_sql, data_lst)

            info = "保存数据{num}条到{schema}.{table}成功, 语句模板为{sql}".format(
                num=len(data_lst), schema=schema, table=table_name, sql=insert_sql)
            return {'status': True, 'result': None, 'info': info}
        except Exception:
            info = f"数据插入表{schema}.{table_name}失败，错误为：%s" % (traceback.format_exc())
            self._logger.error(info)
            return {'status': False, 'result': None, 'info': info}
        finally:
            conn.close()

    def close_resource(self):
        try:
            self._engine.dispose()
        except Exception:
            info = "关闭数据库资源失败，错误为：%s" % traceback.format_exc()
            self._logger.error(info)

    def __enter__(self):
        return self

    def __exit__(self, *args, **kwargs):
        # 支持上下文
        self.close_resource()

